package ar.com.fiuba.domain;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

import ar.com.fiuba.domain.api.NgrammFactory;
import ar.com.fiuba.domain.exceptions.DomainException;

/**
 * Class representing a word in the domain.
 * 
 */
@Entity
@Table(name = "WORD")
public class Word implements Comparable<Word> {

	private Long id;

	private String value;

	private Set<Ngramm> ngramms = new HashSet<Ngramm>();

	@Transient
	private List<String> splittedWord = new ArrayList<String>();

	private NgrammFactory ngrammFactory;

	/**
	 * Constructor.
	 * 
	 * @param value
	 *            String value for the word.
	 * @param ngrammLength
	 *            length for the n-gramms to be generated.
	 * @throws DomainException
	 *             if any problem with the length of the word or the n-gramm
	 *             length.
	 */
	public Word(String value, int ngrammLength, NgrammFactory ngrammFactory) throws DomainException {
		super();
		this.ngrammFactory = ngrammFactory;
		if (!isValidLength(value, ngrammLength))
			throw new DomainException("Invalid ngramm length (" + String.valueOf(ngrammLength) + ") for word's '"
					+ value + "' length (" + value.length() + ").");
		this.value = value;
		generateNgramms(ngrammLength);
	}
	
	private Word() {
		this.value = "";
	}

	/**
	 * Factory method for invalid word.
	 * 
	 * @return an invalid word.
	 * 
	 */
	public static Word NullWord() {
		return new Word();
	}

	/**
	 * Method to generate n-gramms.
	 * 
	 * @param length
	 *            length of the n-gramms.
	 */
	private void generateNgramms(int length) {

		String regularCharacters = "a-z";
		String specialCharacters = "ñáéíóúü";
		char eolChar = '$';
		String charactersRegex = "[\\" + eolChar + regularCharacters + specialCharacters + "]{#}";

		String regex = charactersRegex.replace("#", Integer.toString(length));
		Pattern pattern = Pattern.compile(regex);
		String processedWord = eolChar + value.toLowerCase() + eolChar;
		Matcher matcher = pattern.matcher(processedWord);
		int end = processedWord.length();
		ngramms.clear();
		while (matcher.find()) {
			String ngramm = matcher.group();
			splittedWord.add(ngramm);
			ngramms.add(ngrammFactory.makeNgramm(ngramm));
			int start = matcher.start();
			matcher.region(start + 1, end);
		}
	}

	public int length() {
		return value.length();
	}

	private boolean isValidLength(String value, int ngrammLength) {
		return !(ngrammLength <= 1 || ngrammLength > value.length() + 2 || value.length() == 0);
	}

	@ManyToMany(cascade = CascadeType.ALL, fetch = javax.persistence.FetchType.EAGER, targetEntity = Ngramm.class)
	@JoinTable(name = "WORD_NGRAMM", joinColumns = { @JoinColumn(name = "WORD_ID") }, inverseJoinColumns = { @JoinColumn(name = "NGRAMM_ID") })
	public Set<Ngramm> getNgramms() {
		return ngramms;
	}
	
	public void setNgramms(Set<Ngramm> ngramms) {
		this.ngramms = ngramms;
	}

	@Transient
	public List<Ngramm> getNgrammList() {
		List<Ngramm> result = new ArrayList<Ngramm>();
		for (String ngrammValue : splittedWord) {
			for (Ngramm currentNgramm : ngramms) {
				if (currentNgramm.getValue().equals(ngrammValue)) {
					result.add(currentNgramm);
				}
			}
		}
		return result;
	}
	
	@Id
	@GeneratedValue
	@Column(name="id")
	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	@Column(name = "value")
	public String getValue() {
		return value;
	}

	public void setValue(String value) {
		this.value = value;
	}

	public boolean equals(Object obj) {
		return ((obj != null) && (this.value.equals(((Word) obj).getValue())));
	}
	
	public String toString() {
		return this.value;
	}

	public int compareTo(Word anotherWord) {
		return this.value.compareTo(anotherWord.getValue());
	}

}
